/**
 * Created with JetBrains PhpStorm.
 * User: Ari
 * Date: 8/1/13
 * Time: 8:40 PM
 * To change this template use File | Settings | File Templates.
 */

(function(){
    var DEBUG = document.location.host !== 'localhost';

    var CLS_FORM = 'form-holdem';

    var CLS_HOLDEM_LOG    = 'holdem-log';

    var CLS_STATE_SITTING = 'state-sitting';
    var CLS_STATE_STARTED = 'state-started';
    var CLS_STATE_NOT_STARTED = 'state-not-started';

    var CLS_FIELDSET_PLAYERS = 'fieldset-players';
    var CLS_FIELDSET_CARDS = 'fieldset-cards';
    var CLS_FIELDSET_BOARD = 'fieldset-board';
    var CLS_FIELDSET_BURN = 'fieldset-burn';
    var CLS_FIELDSET_PLAYER = 'fieldset-player';
    var CLS_FIELDSET_TABLE = 'fieldset-holdem-table';
    var CLS_FIELD_MESSAGE = 'field-message';

    var CLS_LEGEND_PLAYER = 'legend-player';

    var GROUP_BOARD = 'board';
    var GROUP_BURN = 'burn';

    var EVENT_BLINDS        = 'blinds';
    var EVENT_BUY_IN        = 'buy-in';
    var EVENT_SMALL_BLIND   = 'small';

    var EVENT_BUTTON        = 'button';

    var EVENT_DEAL          = 'deal';
    var EVENT_LOSE          = 'lose';
    var EVENT_WIN           = 'win';
    var EVENT_WIN_HAND      = 'win-hand';

    var EVENT_CHECK         = 'check';
    var EVENT_READY         = 'ready';
    var EVENT_FOLD          = 'fold';
    var EVENT_CALL          = 'call';
    var EVENT_BET           = 'bet';
    var EVENT_RAISE         = 'raise';
    var EVENT_ALLIN         = 'all-in';
    var EVENT_MESSAGE       = 'msg';
    var EVENT_ERROR         = 'error';
    var EVENT_DEBUG         = 'debug';
    var EVENT_ROUND_BEGIN   = 'round-begin';
    var EVENT_NEW_ROUND     = 'new-round';

    var EVENT_SIDE_POT      = 'sidepot';

    var PARAM_MESSAGE     = 'message';
    var PARAM_TABLE_ID    = 'id';
    var PARAM_ROUND_ID    = 'round-id';
    var PARAM_BUY_IN      = 'buy-in';
    var PARAM_BLINDS      = 'blinds';
    var PARAM_COMMAND     = 'command';
    var PARAM_WALLET      = 'wallet-id' ;
    var PARAM_CURRENT_BET = 'current-bet';
    var PARAM_AMOUNT      = 'amount';
    var PARAM_AUTH_AMOUNT = 'auth-amount';

    // Table options

    var EVENT_WATCH         = 'watch';

    var EVENT_DELETE        = 'delete';
    var EVENT_CREATE        = 'create';

    var EVENT_SIT           = 'sit';
    var EVENT_STAND         = 'stand';
    var EVENT_DEALER        = 'dealer';

    var OPTION_BUY_IN     = 'buy-in';

    var META_DOMAIN_PATH  = 'domain-path';

    var COMMAND_POLL_START = 'poll-start';
    var COMMAND_POLL_ABORT = 'poll-abort';
    var COMMAND_ADD = 'add';
    var COMMAND_PATH = 'path';
    var COMMAND_ERROR = 'error';

    var WORKER_PATH       = 'BC/User/assets/shared-socket.js';

    var META_SESSION = 'session';
    var META_SESSION_ID = 'session-id';

    if(typeof window._holdem_js === 'undefined') window._holdem_js =
    jQuery(document).ready(function() {
        //jQuery(window).resize(onResize);

        var Body = jQuery('body');
        var domainPath = jQuery('head meta[name=' + META_DOMAIN_PATH + ']').attr('content');

        if(typeof openpgp._worker_init === 'undefined') {
            var src = jQuery('script[src$=openpgp\\.js], script[src$=openpgp\\.min\\.js]').attr('src');
            src = src.replace('/openpgp.', '/openpgp.worker.');
            openpgp.initWorker(src);
            openpgp._worker_init = true;
        }

        var decodeBase64 = function(s) {
            var e={},i,b=0,c,x,l=0,a,r='',w=String.fromCharCode,L=s.length;
            var A="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
            for(i=0;i<64;i++){e[A.charAt(i)]=i;}
            for(x=0;x<L;x++){
                c=e[s.charAt(x)];b=(b<<6)+c;l+=6;
                while(l>=8){((a=(b>>>(l-=8))&0xff)||(x<(L-2)))&&(r+=w(a));}
            }
            return r;
        };


        var sessionDecrypt = function (cardID, onSuccess, onError) {
            var pgpData = decodeBase64(cardID);
            var armoredMessage = openpgp.armor.encode(openpgp.enums.armor.message, pgpData);

            var pgpMessage = openpgp.message.readArmored(armoredMessage);
            var encIDs = pgpMessage.getEncryptionKeyIds();

            var sessionKeyring = new openpgp.Keyring.localstore('session-');
            var sessionPKs = sessionKeyring.loadPrivate();
            for(var i=0; i<sessionPKs.length; i++) {
                var key = sessionPKs[i];
                var pk = key.getKeyPacket(encIDs);
                if(pk) {

                    openpgp.decryptMessage(key, pgpMessage)
                        .then(function(decryptedMessage) {
                            onSuccess(decryptedMessage);

                        }).catch(function(e) {
                            if(onError)
                                onError(e);
                        });
                    return;
                }
            }
            var err = "Private keys not found for encryption ids: ";
            for(i=0; i<encIDs.length; i++)
                err += (i ? ', ' : '') + encIDs[i].toHex();
            onError(new Error(err));
        };

        Body.on("ready", function(e) {

            jQuery('form.' + CLS_FORM).each(function(i, form) {
                if(typeof form.holdemInit !== 'undefined')
                    return;
                form.holdemInit = true;

                var LegendPlayer;
                var tableHref, roundOffset, creatorFingerprint, tablePath, betAmount;
                var buyInMin, buyInMax, smallBlind, bigBlind;

                var roundStarted = false;
                var oldHeight = 0;
                var dealerID = null;
                var sessionPlayerID = jQuery('head meta[name=' + META_SESSION_ID + ']').attr('content');
                var lastMessage = null;
                var playerBalances = {};
                var playerSessions = {};
                var playerBets = {};
                var playerFolds = {};
                var playerChecks = {};
                var playerReady = {};
                var cardGroups = {};
                cardGroups[GROUP_BOARD] = [];
                cardGroups[GROUP_BURN] = [];
                var currentBet = null;

                var Form = jQuery(form);
                var CommandButtons = {};
                Form.find('[name=' + PARAM_COMMAND + ']').each(function(i, btn) {
                    CommandButtons[btn.getAttribute('value')] = jQuery(btn);
                });
                var Fields = {};
                Fields[PARAM_CURRENT_BET] = Form.find('input[name=' + PARAM_CURRENT_BET + ']');
                Fields[PARAM_AMOUNT] = Form.find('input[name=' + PARAM_AMOUNT + ']');
                Fields['#range'] = Fields[PARAM_AMOUNT];

                var DivLog = Form.find('.' + CLS_HOLDEM_LOG + ':first');
                var PrettyLog = DivLog.next('div.log-output');
                if (PrettyLog.length === 0) {
                    PrettyLog = jQuery('<ul class="' + CLS_HOLDEM_LOG + ' log-output"/>');
                    DivLog.after(PrettyLog);
                }

                var FieldSetPlayers = Form.find('.' + CLS_FIELDSET_PLAYERS);

                var getMaximumBet = function() {
                    var max = playerBalances[sessionPlayerID];
                    for(var playerID in playerBalances)
                        if(playerBalances.hasOwnProperty(playerID))
                            if(playerFolds[playerID] !== true)
                                if(playerBalances[playerID] < max)
                                    max = playerBalances[playerID];
                    return max;
                };

                var resetRound = function() {
                    //playerTurns = playerTurns.slice(0, buttonPos).concat(playerTurns.slice(buttonPos));
                    cardGroups = {};
                    cardGroups[GROUP_BOARD] = [];
                    cardGroups[GROUP_BURN] = [];
                    playerBets = {};
                    currentBet = 0;
                    Form.find('.' + CLS_FIELDSET_CARDS + ' .card').fadeOut(function() {
                        jQuery(this).remove();
                    })
                };

                var fadeDelay = 0;
                var decryptCard = function (i, spanElm) {
                    if(typeof spanElm.decrypted !== "undefined") {
                        return spanElm.decrypted;
                    }
                    spanElm.decrypted = null;
                    var SpanCard = jQuery(spanElm);
                    SpanCard.addClass('card-decrypting');
                    var cardID = SpanCard.html();
                    fadeDelay = 0;
                    if(jQuery.isNumeric(cardID)) {
                        spanElm.decrypted = true;
                        spanElm.decryptedValue = cardID;
                        SpanCard
                            .addClass("C" + cardID)
                            .removeClass('card-encrypted');
                        return true;
                    }
                    sessionDecrypt(cardID, function (decryptedCard) {
                        spanElm.decrypted = true;
                        spanElm.decryptedValue = decryptedCard;
                        SpanCard.fadeOut(function () {
                            SpanCard
                                .addClass("C" + decryptedCard)
                                .removeClass('card-encrypted')
                                .removeClass('card-decrypting')
                                .fadeIn(fadeDelay += 100);
                        });
                    }, function(err) {
                        spanElm.decrypted = false;
                        SpanCard
                            .addClass('card-encrypted')
                            .fadeIn(fadeDelay += 100);
                        jQuery(spanElm).trigger('error', [err.message || error]);
                    });
                };

                //var socketWorker = new Worker(domainPath + WORKER_PATH); // TODO: use in background
                var watchPaths = [];
                var CLIENT_PORT = 7845;
                var CLIENT_HOST = 'localhost';
                var RECONNECT_TIMEOUT = 2000;
                var RECONNECT_MAX = 30000;
                var WEB_SOCKET_URL = 'ws://' + document.location.host + ':' + CLIENT_PORT + '/listen';

                var webSocket = null;
                var startWebSocket = function () {
                    webSocket = new WebSocket(WEB_SOCKET_URL);
                    form.webSocket = webSocket;
                    webSocket.addEventListener('open', function(e) {
                        for(var i=0; i<watchPaths.length; i++)
                            webSocket.send(EVENT_WATCH + ' ' + watchPaths[i].join(' '));
                    });
                    webSocket.addEventListener('message', function (e) {
                        processLine(e && e.data ? e.data : "No data");
                    }, false);
                    webSocket.addEventListener('close', function (e) {
                        //console.error("WebSocket closed. Reconnecting in " + RECONNECT_TIMEOUT, e);
                        setTimeout(startWebSocket, RECONNECT_TIMEOUT);
                        if(RECONNECT_TIMEOUT < RECONNECT_MAX)
                            RECONNECT_TIMEOUT += 1000;
                    }, false);
                };
                startWebSocket();

                var sendCommand = function(args) {
                    if(webSocket.readyState === WebSocket.OPEN) {
                        var command = tablePath + '?' + jQuery.param(args);
                        console.log("Socket: ", command);
                        webSocket.send(command);
                    } else {
                        console.log("POST: ", tablePath, args);
                        jQuery.post(domainPath + tablePath, args, function(content) {

                        }, 'json');
                    }
                };

                var isAllIn = function() {
                    return (playerBets[sessionPlayerID] === playerBalances[sessionPlayerID]);
                };
                var reasonGiven = null;

                var canBetAllIn = function() {
                    if(!canBet())
                        return false;
                    if(isAllIn())
                        return false;

                    return getMaximumBet() === playerBalances[sessionPlayerID];
                };

                var canBet = function() {
                    var bet = playerBets[sessionPlayerID];

                    if(!roundStarted) {
                        reasonGiven = "Round is not started";
                        return false;
                    }

                    if(bet >= playerBalances[sessionPlayerID]) {
                        reasonGiven = "Insufficient balance for player '" + sessionPlayerID + "'";
                        return false;
                    }

                    if(bet < currentBet) {
                        reasonGiven = "Waiting on " + sessionPlayerID + " to bet or fold";
                        return true;
                    }

                    if(bet == currentBet) {
                        var waiting = 0;
                        var unfolded = 0;
                        for (var pID in playerBets) {
                            if (playerBets.hasOwnProperty(pID)) {
                                if(playerFolds[pID] !== true) {
                                    unfolded++;
                                    if(playerBets[pID] < bet) {
                                        waiting++;
                                    }
                                }
                            }
                        }

                        if(unfolded === 1) {
                            reasonGiven = sessionPlayerID + " is the last remaining player";
                            return false;
                        }

                        if(waiting) {
                            reasonGiven = "Waiting on " + waiting + " player(s) with lower bets to bet or fold";
                            return false;
                        }

                        if(bet === smallBlind || bet === bigBlind) {
                            reasonGiven = "Player may bet beyond the blinds";
                            return true;
                        }

                        if(bet >= getMaximumBet()) {
                            reasonGiven = "Player " + sessionPlayerID + " has reached maximum bet";
                            return false;
                        }

                        if(playerChecks[sessionPlayerID] !== true) {
                            reasonGiven = "Waiting on player " + sessionPlayerID + " to check";
                            return true;
                        }

                        reasonGiven = "Player has highest bet";
                        return false;
                    }

                    if(playerBets[sessionPlayerID] === playerBalances[sessionPlayerID]) {
                        reasonGiven = "Player " + sessionPlayerID + " is all-in";
                        return false;
                    }

                    reasonGiven = "Player '" + sessionPlayerID + "' has highest bet";
                    return false;
                };

                var canCall = function() {
                    if(playerBets[sessionPlayerID] >= currentBet) {
                        reasonGiven = "Player " + sessionPlayerID + "'s bet is already gte the current bet";
                        return false;
                    }
                    return canBet();
                };

                var canFold = function() {
                    if(!roundStarted) {
                        reasonGiven = "Round is not started";
                        return false;
                    }

                    if(playerFolds[sessionPlayerID] === true) {
                        reasonGiven = sessionPlayerID + " player has already folded";
                        return false;
                    }

                    if(!canBet())
                        return false;

                    reasonGiven = "Player " + sessionPlayerID + " has not folded";
                    return true;
                };

                var canCheck = function() {
                    if(!roundStarted) {
                        reasonGiven = "Round is not started";
                        return false;
                    }

                    if(isAllIn())
                        return false;

                    if(playerChecks[sessionPlayerID] === true) {
                        reasonGiven = sessionPlayerID + " player has already checked";
                        return false;
                    }

                    reasonGiven = "Player " + sessionPlayerID + " has not checked";
                    return true;
                };

                var updateActions = function(playerBet) {
                    if(!playerBet)
                        playerBet = Fields[PARAM_CURRENT_BET].val();

                    Fields['#range'].attr('max', getMaximumBet());
                    Fields['#range'].attr('min', currentBet + bigBlind);

                    if(playerBet < currentBet)
                        playerBet = currentBet;

                    if(playerBet < currentBet + bigBlind)
                        playerBet = currentBet + bigBlind;

                    if(playerBet > getMaximumBet())
                        playerBet = getMaximumBet();

                    if(Fields[PARAM_AMOUNT].val() !== playerBet)
                        Fields[PARAM_AMOUNT].val(playerBet);
                    if(Fields[PARAM_CURRENT_BET].val() !== playerBet)
                        Fields[PARAM_CURRENT_BET].val(playerBet);

                    CommandButtons[EVENT_FOLD].removeAttr('disabled');
                    canBet() ? CommandButtons[EVENT_BET].removeAttr('disabled') : CommandButtons[EVENT_BET].attr('disabled', 'disabled');
                    canCall() ? CommandButtons[EVENT_CALL].removeAttr('disabled') : CommandButtons[EVENT_CALL].attr('disabled', 'disabled');

                    canBetAllIn() ? CommandButtons[EVENT_ALLIN].removeAttr('disabled') : CommandButtons[EVENT_ALLIN].attr('disabled', 'disabled');
                    canCheck() ? CommandButtons[EVENT_CHECK].removeAttr('disabled') : CommandButtons[EVENT_CHECK].attr('disabled', 'disabled');

                    canFold() ? CommandButtons[EVENT_FOLD].removeAttr('disabled') : CommandButtons[EVENT_FOLD].attr('disabled', 'disabled');

                    !roundStarted && playerReady[sessionPlayerID] !== true ? CommandButtons[EVENT_READY].removeAttr('disabled') : CommandButtons[EVENT_READY].attr('disabled', 'disabled');

                    canBet() ? Fields[PARAM_AMOUNT].removeAttr('disabled') : Fields[PARAM_AMOUNT].attr('disabled', 'disabled');
                    canBet() ? Fields[PARAM_CURRENT_BET].removeAttr('disabled') : Fields[PARAM_CURRENT_BET].attr('disabled', 'disabled');

                    if(currentBet)
                        CommandButtons[EVENT_CALL].text('Call to ' + currentBet);
                    if(typeof playerBets[sessionPlayerID] === 'integer')
                        CommandButtons[EVENT_FOLD].text('Fold ' + playerBets[sessionPlayerID]);
                    CommandButtons[EVENT_FOLD].text('Fold');
                    CommandButtons[EVENT_ALLIN].text('All in at ' + getMaximumBet());

                    FieldSetPlayers.find('fieldset[data-player-id]').each(function(i, elm) {
                        var FieldSet = jQuery(elm);
                        var playerID = FieldSet.attr('data-player-id');
                        var html = "<span class='playerID'>" + playerID + "</span> - ";
                        if(typeof playerBets[playerID] !== 'undefined')
                            html += playerBets[playerID] + ':';
                        html += playerBalances[playerID];

                        FieldSet.find('legend.' + CLS_LEGEND_PLAYER).html(html);
                    });
                };

                var lastEventName = null;
                var processLine = function (line, nextLine) {
                    if (!line)
                        return;

                    var playerID;
                    var args = line.split(' ');
                    var eventName = args[0];
                    switch (eventName) {
                        case COMMAND_PATH:
                            line = '';
                            //line = args.join(' ');
                            break;

                        case EVENT_WATCH:
                            var logPath = args[1];
                            var offset = args[2];
                            watchPaths.push([logPath, offset]);
                            line = '';
                            break;

                        case EVENT_CREATE:
                            creatorFingerprint = args[1];
                            tablePath = args[2];
                            tableHref = domainPath + tablePath;
                            var creatorHref = domainPath + '/user/' + creatorFingerprint;
                            line = "<a href='" + tableHref + "'>Table</a> created by <a class='fingerprint' href='" + creatorHref + "'>" + creatorFingerprint + "</a>.";
                            break;

                        case EVENT_WIN_HAND:
                            args.shift();
                            var handWinnerID = args.shift();
                            var handName = args.shift().replace(/_/g, ' ');
                            var cardHTML = [];
                            var winPair = [];
                            var endHighlight = false;
                            for(var i=0; i<args.length; i++) {
                                if(jQuery.isNumeric(args[i])) {
                                    var card = parseInt(args[i]);
                                    var highlight = endHighlight ? ' kicker' : ' highlight';
                                    cardHTML.push("<span class='card medium C" + args[i] + highlight + "'>" + args[i] + "</span>");
                                    if(cardGroups[GROUP_BOARD].indexOf(card.toString()) !== -1) {

                                    } else {
                                        winPair.push(card);
                                    }
                                } else if(!args[i]) {
                                    endHighlight = true;

                                } else {
                                    cardHTML.push(args[i]);
                                }
                            }

                            line = "Player <span class='playerID'>" + handWinnerID + "</span>" +
                                " wins with <span class='winning-hand'>" + handName + "</span>" +
                                "<br/><div class='cards-hand'>" + cardHTML.join("") + "</div>";


                            if(winPair.length !== 2) {
                                console.error("Winner pair could not be determined");
                            } else {
                                cardGroups[handWinnerID] = winPair;
                                var WinnerFieldSetPlayer = FieldSetPlayers.find('fieldset[data-player-id=' + handWinnerID + ']');
                                WinnerFieldSetPlayer.find('.card').remove();
                                WinnerFieldSetPlayer.append("<div class='card C" + winPair[0] + "'>" + winPair[0] + "</div>");
                                WinnerFieldSetPlayer.append("<div class='card C" + winPair[0] + "'>" + winPair[0] + "</div>");
                            }


                            roundStarted = false; // TODO: make show up in order
                            playerReady = {};
                            updateActions();

                            break;

                        case EVENT_WIN:
                            var winnerID = args[1];
                            var amount = parseInt(args[2]);
                            playerBalances[winnerID] += amount;
                            line = "Player <span class='playerID'>" + winnerID + "</span> wins <span class='amount'>" + amount + "</span>.";

                            if(winnerID === sessionPlayerID) {

                                line += "<br/>Great jorb! Would you like to <span class='tip'>tip</span> the <span class='dealer'>dealer</span>?" +
                                "<br/>Donate <input size='1' value='1' /> to " +
                                "<select name='input-donate-stream' style='width:120pt'>" +
                                "<optgroup label='Contribution Streams (people who contributed to this game)'>" +
                                    "<option>Developers Pool</option>" +
                                    "<option>Server Costs and Maintenance</option>" +
                                "</optgroup>" +
                                "<optgroup label='Investment Streams (funding for public projects like this one)'>" +
                                    "<option>Free public games</option>" +
                                    "<option>Financial tools</option>" +
                                    "<option>Communication tools</option>" +
                                    "<option>Select project at random</option>" +
                                "</optgroup>" +
                                "<optgroup label='Charity Streams'>" +
                                    "<option>Charity XYZ</option>" +
                                    "<option>Select Charity at random</option>" +
                                "</optgroup>" +
                                "<optgroup label='Other Options'>" +
                                    "<option>Donate to Stream at random</option>" +
                                "</optgroup>" +
                                "</select>"
                            }

                            roundStarted = false;
                            playerReady = {};
                            updateActions();

                            break;

                        case EVENT_LOSE:
                            var loserID = args[1];
                            var loserAmount = parseInt(args[2]);
                            playerBalances[loserID] -= loserAmount;
                            line = "Player <span class='playerID'>" + loserID + "</span> loses <span class='amount-lose'>" + loserAmount + "</span>.";
                            break;

                        case EVENT_READY:
                            playerID = args[1];
                            playerReady[playerID] = true;
                            if(playerID === sessionPlayerID)
                                updateActions();
                            line = "Player <span class='playerID'>" + playerID + "</span> is <span class='ready'>ready</span>.";
                            break;

                        case EVENT_NEW_ROUND:
                            roundOffset = args[1];
                            resetRound();
                            line = null;
                            break;

                        case EVENT_ROUND_BEGIN:
                            roundStarted = true;

                            line = "<fieldset><legend>New round begins</legend>";
                            for(playerID in playerBalances)
                                if(playerBalances.hasOwnProperty(playerID))
                                    line += "&nbsp; <span class='playerID'>" + playerID + "</span>: <span class='balance'>" + playerBalances[playerID] + "</span><br/>";
                            line += "</fieldset>";
                            break;

                        case EVENT_BUTTON:
                            playerID = args[1];
                            //var buttonPos = playerTurns.indexOf(playerID);
                            line = "Player <span class='playerID'>" + playerID + "</span> becomes the <span class='button'>button</span>.";
                            break;

                        case EVENT_SIT:
                            playerID = args[1];
                            var sessionID = args[2];
                            var balance = parseInt(args[3]);

                            //playerTurns.push(playerID);
                            playerSessions[playerID] = sessionID;
                            playerBalances[playerID] = balance;
                            playerBets[playerID] = 0;

                            //if(playerID == sessionPlayerID) {
                            //    CommandButtons[EVENT_BET].attr('disabled', 'disabled');
                            //    CommandButtons[EVENT_CALL].attr('disabled', 'disabled');
                            //    CommandButtons[EVENT_ALLIN].attr('disabled', 'disabled');
                            //    CommandButtons[EVENT_CHECK].attr('disabled', 'disabled');
                            //    CommandButtons[EVENT_FOLD].attr('disabled', 'disabled');
                            //    Fields[PARAM_AMOUNT].attr('disabled', 'disabled');
                            //    Fields[PARAM_CURRENT_BET].attr('disabled', 'disabled');
                            //}

                            var sessionHref = domainPath + '/session/' + sessionID;
                            line = "Player <a href='" + sessionHref + "'>" + playerID + "</a> sits at the <a href='" + tableHref + "'>table</a> with <span class='buy-in'>" + balance + "</span>.";
                            var SitFieldSetPlayer = FieldSetPlayers.find('fieldset[data-player-id=' + playerID + ']');
                            var playerCSS = CLS_FIELDSET_PLAYER + '-' + playerID;
                            if (SitFieldSetPlayer.length === 0) {
                                FieldSetPlayers.append(
                                    SitFieldSetPlayer = jQuery('<fieldset class="' + CLS_FIELDSET_PLAYER + ' ' + playerCSS + '" data-player-id="' + playerID + '">' +
                                    '<legend class="' + CLS_LEGEND_PLAYER + '"></legend>' +
                                    '</fieldset>')
                                )
                            }

                            updateActions();

                            break;

                        case EVENT_STAND:
                            playerID = args[1];
                            //playerTurns.splice(playerTurns.indexOf(playerID), 1);

                            line = "Player <a href='" + sessionHref + "'>" + playerID + "</a> stands up from the <a href='" + tableHref + "'>table</a>.";
                            FieldSetPlayers
                                .find('fieldset[data-player-id=' + playerID + ']')
                                .remove();
                            break;

                        case EVENT_DEALER:
                            dealerID = args[1];
                            line = "Table dealer is " + dealerID;
                            break;

                        case EVENT_DEAL:
                            playerID = args[1];
                            var cardID = args[2];
                            //var cardSig = args[3];

                            if (typeof cardGroups[playerID] == 'undefined')
                                cardGroups[playerID] = [];
                            cardGroups[playerID].push(cardID);

                            playerChecks = {};
                            updateActions();

                            switch(playerID) {
                                case GROUP_BURN:
                                    var divDealBurnCardHTML = "<div class='card card-encrypted card-burn-" + cardGroups[playerID].length + "'>" + cardID + "</div>";
                                    line = "Dealer burns card";
                                    var FieldSetBurn = Form.find('.' + CLS_FIELDSET_BURN);
                                    FieldSetBurn.append(divDealPlayerCardHTML);
                                    break;

                                case GROUP_BOARD:
                                    var divDealBoardCardHTML = "<div class='card card-board-" + cardGroups[playerID].length + " C" + cardID + "'>" + cardID + "</div>";
                                    line = "Dealer deals to <span class='board'>" + playerID + "</span>: " + divDealBoardCardHTML;
                                    var FieldSetBoard = Form.find('.' + CLS_FIELDSET_BOARD);
                                    FieldSetBoard.append(divDealBoardCardHTML);
                                    break;

                                default:
                                    if(cardGroups[playerID].length > 2)
                                        throw new Error("More than 2 cards for player: " + playerID);

                                    var FieldSetPlayer = FieldSetPlayers.find('fieldset[data-player-id=' + playerID + ']');

                                    var playerCardID = cardGroups[playerID].length;
                                    var playerCardClass = "card-player-" + playerID + '-' + playerCardID;
                                    var divDealPlayerCardHTML = "<div class='card card-encrypted " + playerCardClass + "'>" + cardID + "</div>";
                                    if(playerCardID === 2) {
                                        var SpanFirstCard = PrettyLog.find(".card-player-" + playerID + '-1:last');
                                        if(!SpanFirstCard.length)
                                            throw new Error("Could not find last player card");
                                        SpanFirstCard.after(divDealPlayerCardHTML);
                                        line = null;
                                    } else {
                                        line = "Dealer deals to <span class='playerID'>" + playerID + "</span>: " + divDealPlayerCardHTML;
                                        FieldSetPlayer.find('.card').remove();
                                    }

                                    FieldSetPlayer.append(divDealPlayerCardHTML);
                                    if(playerID === sessionPlayerID) {
                                        setTimeout(function() {
                                            FieldSetPlayer.find('.' + playerCardClass).each(decryptCard);
                                            PrettyLog.find('.' + playerCardClass).each(decryptCard);
                                        }, 1);
                                    }
                                    break;
                            }

                            break;

                        case EVENT_FOLD:
                            playerID = args[1];
                            //playerMoves[playerID] = (playerMoves[playerID] | 0) + 1;
                            playerFolds[playerID] = true;
                            var FoldFieldSetPlayer = FieldSetPlayers.find('fieldset[data-player-id=' + playerID + ']');
                            var FoldLegendPlayer = FoldFieldSetPlayer.children('legend.legend-player');
                            FoldLegendPlayer.html("<span class='playerID'>" + playerID + "</span> - folded");

                            updateActions();

                            line = "<span class='playerID'>" + playerID + "</span> <span class='folds'>folds</span>.";
                            break;

                        case EVENT_CHECK:
                            playerID = args[1];
                            playerChecks[playerID] = true;

                            updateActions();

                            line = "<span class='playerID'>" + playerID + "</span> <span class='checks'>checks</span>.";
                            break;

                        case EVENT_CALL:
                        case EVENT_BET:
                        case EVENT_RAISE:
                        case EVENT_ALLIN:

                            playerID = args[1];
                            //playerMoves[playerID] = (playerMoves[playerID] | 0) + 1;
                            var totalBet = parseInt(args[2]);
                            var oldBet = playerBets[playerID] | 0;
                            playerBets[playerID] = totalBet;
                            if(totalBet < currentBet)
                                console.error("Player bet too low: " + totalBet + ". Current bet: " + currentBet);

                            var betType = "<span class='calls'>calls</span> with";
                            if(totalBet > currentBet) {
                                betType = "<span class='bets'>bets</span>";
                                // Bet changes
                                currentBet = totalBet;
                            }
                            // TODO blinds
                            if(totalBet === playerBalances[playerID])
                                betType = "is <span class='all-in'>all in</span> with";

                            updateActions();

                            line = "<span class='playerID'>" + playerID + "</span> " + betType + " <span class='bet-amount'>" + (totalBet - oldBet) + "</span>. Bet is now at <span class='bet-amount'>" +  currentBet + "</span>.";
                            break;

                        case EVENT_BLINDS:
                            smallBlind = parseInt(args[1]);
                            bigBlind = parseInt(args[2]);
                            line = "Blinds at <span class='blind-amount'>" + smallBlind + "</span> : <span class='blind-amount'>" + bigBlind + "</span>.";
                            if (typeof args[3] === 'string')
                                line = "<span class='playerID'>" + args[3] + "</span> sets " + line;
                            break;

                        case EVENT_BUY_IN:
                            buyInMin = args[1];
                            buyInMax = args[2];
                            line = "Buy-in limits min <span class='buy-in-amount'>" + buyInMin + "</span> to max <span class='buy-in-amount'>" + buyInMax + "</span>.";
                            if (typeof args[3] === 'string')
                                line = "<span class='playerID'>" + args[3] + "</span> sets " + line;
                            break;

                        case EVENT_DEBUG:
                            args.shift();
                            console.debug(args.join(' '));
                            line = null;
                            break;

                        case EVENT_ERROR:
                            args.shift();
                            console.error(args.join(' '));
                            line = "<b>Error: </b><span class='error'>" + args.join(' ') + "</span>.";
                            break;

                        case EVENT_MESSAGE:
                            if (line == lastMessage) {

                            }
                            args.shift();
                            playerID = args.shift();
                            lastMessage = line;
                            line = "<span class='playerID'>" + playerID + "</span> says: <span class='message'>" + args.join(' ') + "</span>";
                            break;

                        //case EVENT_STAND:
                        //    break;
                        default:
                            line = "<b>Unknown command: </b><span class='error'>" + args.join(' ') + "</span>.";
                            console.log("Unknown command: " + eventName);
                            break;
                    }
                    if(line) {
                        PrettyLog.append("<li>" + line + "</li>");
                        if (oldHeight !== PrettyLog[0].scrollHeight){
                            PrettyLog.scrollTop(oldHeight = PrettyLog[0].scrollHeight);}
                    }
                    lastEventName = eventName;
                };

                var processLines = function(content) {
                    var lines = content.split(/\n/);
                    for (i = 0; i < lines.length; i++) {
                        if(DEBUG) {
                            try {
                                processLine(lines[i], i < lines.length ? lines[i+1] : null);
                            } catch (err) {
                                console.error(err);
                                PrettyLog.append("<span class='error'>Error with line:</span> " + lines[i]);
                                if (oldHeight !== PrettyLog[0].scrollHeight)
                                    PrettyLog.scrollTop(oldHeight = PrettyLog[0].scrollHeight);
                            }
                        } else {
                            processLine(lines[i], i < lines.length ? lines[i + 1] : null);
                        }
                    }
                };

                var content = DivLog.text();
                DivLog.text('');
                processLines(content);

                if(typeof form._submit === 'undefined')
                    form._submit = Form.on('click keydown', function(e) {
                        if(e.type === 'keydown' && e.keyCode !== 13)
                            return;

                        var name = e.target.getAttribute('name');
                        if(name !== PARAM_COMMAND)
                            return; // throw new Error("invalid element name: " + e.target);

                        var command = e.target.getAttribute('value');
                        if(!command)
                            throw new Error("No button pressed");
                        e.preventDefault();

                        var Target = jQuery(e.target); // Form.find('[name=' + PARAM_COMMAND + "][value=" + command + "]");

                        switch(command) {
                            case EVENT_MESSAGE:
                                var InputMessage = Target.parents('fieldset').find('input[name=' + PARAM_MESSAGE + ']');
                                var message = InputMessage.val();
                                InputMessage.val('');
                                if(!message)
                                    throw new Error("Invalid message");

                                var messageArgs = {};
                                messageArgs[PARAM_COMMAND] = EVENT_MESSAGE;
                                messageArgs[PARAM_MESSAGE] = message;

                                sendCommand(messageArgs);
                                break;

                            case EVENT_READY:
                                e.preventDefault();
                                var readyArgs = {};
                                readyArgs[PARAM_COMMAND] = command;
                                sendCommand(readyArgs);
                                break;

                            case EVENT_BUY_IN:
                                e.preventDefault();
                                var buyInArgs = {};
                                buyInArgs[PARAM_COMMAND] = command;
                                buyInArgs[PARAM_BUY_IN] = Fields[PARAM_BUY_IN].val();
                                sendCommand(buyInArgs);
                                break;

                            case EVENT_BLINDS:
                                e.preventDefault();
                                var blindsArgs = {};
                                blindsArgs[PARAM_COMMAND] = command;
                                blindsArgs[PARAM_BLINDS] = Fields[PARAM_BLINDS].val();
                                sendCommand(buyInArgs);
                                break;

                            case EVENT_CALL:
                            case EVENT_FOLD:
                            case EVENT_CHECK:
                            case EVENT_BET:
                                e.preventDefault();
                                var amount = Fields[PARAM_AMOUNT].val();

                                var betArgs = {};
                                betArgs[PARAM_COMMAND] = command;
                                betArgs[PARAM_AMOUNT] = Fields[PARAM_AMOUNT].val();
                                sendCommand(betArgs);
                                break;

                            case EVENT_ALLIN:
                                var max = Fields[PARAM_AMOUNT].attr('max');
                                Fields[PARAM_AMOUNT].val(max);
                                Fields[PARAM_CURRENT_BET].val(max);
                                e.preventDefault();
                                break;

                            default:
                                throw new Error("Unknown button: " + e.target);
                        }
                    }).on('input change', function(e) {
                        event.preventDefault();
                        var name = e.target.getAttribute('name');
                        var amount = parseInt(jQuery(e.target).val()) | 0;
                        if(lastInputAmount === amount)
                            return;

                        if(amount < playerBets[sessionPlayerID])
                            amount = playerBets[sessionPlayerID];
                        if(amount !== currentBet && amount < currentBet + bigBlind)
                            amount = currentBet + bigBlind;
                        if(amount > playerBalances[sessionPlayerID])
                            amount = playerBalances[sessionPlayerID];
                        lastInputAmount = amount;
                        switch(name) {
                            case PARAM_CURRENT_BET:
                                if(e.type === 'change' || amount >= playerBalances[sessionPlayerID])
                                    updateActions(amount);
                                break;
                            case PARAM_AMOUNT:
                                updateActions(amount);
                                break;
                            default:
                                break;
                        }
                    });
                var lastInputAmount = null;
            });
        });
        Body.trigger('ready');

    });

})();
